/**
 * @brief 守护进程组件
 * @brief 将进程变成守护进程，不受用户的登录和注销影响
 * *原理是让目的进程变成一个拥有独立会话和进程组的孤儿进程(精灵进程)
*/

#pragma once

#include <cstdlib>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

// 该文件是一个系统文件，类似于垃圾桶，把数据放里面就会清空，也无法从中拿出数据
#define DUSTBIN "/dev/null" 

/**
 * @param aim_work_path 想让进程工作在哪个路径
*/
void myDaemon(const char* aim_work_path = nullptr) {
    // 让进程忽略掉一些异常的信号
    // 当服务端正要对客户端发送信息时，如果客户端突然异常关闭
    // 那么服务端就会收到SIGPIPE信号来终止进程，我们要避免这样的事情出现
    // 这个收到SIGPIPE信号的现象就像进程间管道通信时，如果读端关闭那么写端也收到信号关闭
    signal(SIGPIPE, SIG_IGN);

    // 想要让服务端拥有独立会话需要使用setsid函数
    // 但是setsid要求使用该函数的进程不是进程组长
    // 我们用fork来解决这个问题
    // 我们让主线程fork出一个子进程，然后让主进程终止，子进程继续下面代码中的setsid
    // 但是因为进程组组长是主进程，所以不是组长的子进程就能成功将自己移除当前会话和进程组
    // 自己进入一个新的会话和进程组，且为会话组长(一般为用户的shell)和进程组长
    // 这样既保证了该服务端只有一个进程(主进程已经退出)，也借助子进程变成了保护进程
    if (fork() > 0) exit(EXIT_SUCCESS); // 主进程
    pid_t n = setsid(); // 子进程
    assert(n != -1);

    // 因为守护进程是脱离终端的，所以它不需要从键盘中获取数据，或向屏幕中打印数据
    // 所以我们需要关闭OS为我们打开的三个流
    // 但是如果直接关闭stdin, stdout, stderr三个流可能会引起问题
    // 因为我们的程序(服务端)中可能会向这三个流中发送数据
    // 这时程序就会因为向已经关闭的fd中输出数据而发生错误而导致服务端挂掉
    // 所以我们将这三个fd重定向到垃圾桶中，我们不要这些数据了(反正这些也是用于测试的数据，我们忘记注释或删掉而已)
    int fd = open(DUSTBIN, O_RDWR); // O_RDWR为可读可写
    if (fd >= 0) { // 打开垃圾桶成功
        // 用fd的内容替换0，1，2的内容
        // 这样0、1、2指向的文件就是垃圾桶了
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);

        close(fd); // 我们的目的使用垃圾桶替换掉OS的三个流，我们不需要垃圾桶本身的fd，记得要关掉，避免fd泄漏
    }
    else {
        // 如果垃圾桶没能打开成功就只能强行关掉0、1、2了
        close(0);
        close(1);
        close(2);
    }

    // 切换工作目录，有的话就换，没有的话就不换
    if (aim_work_path) chdir(aim_work_path);
}